全名golang的GO是Google為了改善實務面問題而開發的語言(這裡指的服務當然是網路服務)
因此在語法上會盡量以工程師開發的角度來設計
所以golang雖然是編譯語言,但相較我們學過的java,C#等等已經簡化了許多
看看java要讀取一段字還要先準備一個scanner(以純物件的角度來看這樣做是完全合理的)
而C#則更像是微軟推出的產品,生態,框架都幫你準備好了(看看init幫我們生出的那堆東西)
只是有時候我們真的不需要那麼多
另外golang還提供了官方的程式風格及工具,因此所有工程師的程式碼風格看起來都一樣(不一樣的話甚至不會讓你編譯)
編譯器甚至還會幫你檢查你的程式有沒有多東西,也就是如果你的程式有多餘的程式碼會提醒你,沒刪掉就不讓你編譯
而且相較於我們之前介紹的其他語言(全都誕生於2000年或以前),golang是屬於晚期的語言(誕生於2009年),
但是他不像晚期的語言就取消了指標(先不要問什麼是指標,很可怕)這個強大的功能,反而將他保留了下來並附於各種函式庫中
另外golang的保留字也相對較少只有25個(C# 97個,javascript 63個,java 50個,C++ 49個,python 33個)
如果要直接用保留字來認識這個語言也是可以並且相對簡單的
而且吉祥物很可愛
先去安裝golang吧
Ready?
在你的工作目錄下面建立一個main.go的檔案,然後貼上下面的程式碼
package main
import (
"fmt"
"math"
"strconv"
"strings"
)
func main() {
var input string
fmt.Println("Tell me what you want to do:")
fmt.Println("(1)T to H (2)H to T")
fmt.Scanln(&input)
switch input {
case "1":
var number string
fmt.Println("Please enter the number:")
fmt.Scanln(&number)
fmt.Println(t2h(number))
case "2":
var number string
fmt.Println("Please enter the number:")
fmt.Scanln(&number)
fmt.Println(h2t(number))
default:
fmt.Println("Wrong selection")
}
}
func h2t(number string) (output float64){
inarr := strings.Split(number, "")
i:=0
for i < len(inarr) {
output += AEreturn(inarr[i]) * math.Pow(16, float64(len(inarr)-(i+1)))
i++
}
return
}
func AEreturn(number string) (output float64) {
num, err := strconv.Atoi(number)
if err != nil {
switch number {
case "A":
num = 10
case "B":
num = 11
case "C":
num = 12
case "D":
num = 13
case "E":
num = 14
case "F":
num = 15
default:
num = 0
}
}
output = float64(num)
return
}
func t2h(number string)string {
tenum, _ := strconv.Atoi(number)
for i := 0; i < 16; i++ {
for j := 0; j < 16; j++ {
for k := 0; k < 16; k++ {
if int(math.Pow(16, 2))*i+int(math.Pow(16, 1))*j+int(math.Pow(16, 0))*k == tenum {
return fmt.Sprintf("%s%s%s",returnAE(i),returnAE(j),returnAE(k))
}
}
}
}
return ""
}
func returnAE(remain int) string {
if remain < 10 {
return strconv.Itoa(remain)
} else {
switch remain {
case 10:
return "A"
case 11:
return "B"
case 12:
return "C"
case 13:
return "D"
case 14:
return "E"
case 15:
return "F"
default:
return "wrong"
}
}
}
golang的行尾是不需要;的,當然你想加也是可以
使用
go run main.go
就可以將你的程式執行起來了
golang最大的結構稱為package(甚至可以橫跨好幾個檔案)
但是一個資料夾內只能有一種package(你可以把package分成多個檔案,但是package名稱必須相同)
宣告的方式如下
package main
所有的方法變數都會包裝在package裡面
(不像C#使用{}來包裝namespace,當package在第一行宣告時,整個檔案都包在這個package裡面,因此基本上是不可能超出去的)
而由於golang屬於編譯式語言,我們必須宣告出程式的進入點
因此當我們執行程式時golang會自動去找工作資料夾內package為main的main方法
也就是這裡
package main
...
func main()
這裡我們建立了一個main的package表示這裡是我們的主程式
而主程式裡面的main方法就是主程式的進入點
package也能當作是一個函式庫
如果你需要在一個專案內引入package可以使用import
以下我們引入的這些函式庫都是一個個由官方提供的package喔
import (
"fmt"
"math"
"strconv"
"strings"
)
當然,你也可以使用自定義的package,不過使用上會建議直接學go mod幫助你管理package
(當年剛開始學的時候go mod還沒開始流行,每次都要被gopath搞死)
今天我們先不學如何使用package,畢竟只是單純介紹語言
以上就是golang最基本的結構拉(其實import嚴格上不算,不過誰寫程式不會用到函式庫呢?)
一般而言golang的main方法是不會有輸入值跟回傳值的
func main() {}
但是其他的副程式可能會有,往下看到這裡
func t2h(number string)string {}
這裡定義了輸入值與回傳值的型別,都是string
golang比較特別的是型別宣告位於變數名稱之後而不是之前
當你不需要輸入值或回傳值的時候就空著就好
golang也能在方法宣告時就把回傳值的變數名稱給定義好
比方說
func h2t(number string) (output float64){ //這裡定義了回傳值的變數名稱與型別
inarr := strings.Split(number, "")
for i := 0; i < len(inarr); i++ {
output += AEreturn(inarr[i]) * math.Pow(16, float64(len(inarr)-(i+1)))
}
return //這裡會直接把output回傳
}
相當於
func h2t(number string) float64{ //這裡只定義型別,沒有定義名稱
var output float64 = 0 //這裡建立了一個準備要回傳的變數
inarr := strings.Split(number, "")
for i := 0; i < len(inarr); i++ {
output += AEreturn(inarr[i]) * math.Pow(16, float64(len(inarr)-(i+1)))
}
return output //因此回傳時就需要回傳變數
}
這樣會讓你的程式比較精簡,也能一眼看出回傳值的位置與名稱
接著讓我們回到main方法的內容吧
var input string
fmt.Println("Tell me what you want to do:")
fmt.Println("(1)T to H (2)H to T")
fmt.Scanln(&input)
這個就是golang輸入及輸出的方法,都來自這個叫fmt的package
(其實輸出還有其他種方式,不過我們就先介紹這種)
我注意到input之前有一個&, 這是什麼意思?
一般而言我們將變數帶入方法時,是傳遞變數的名稱,但是在這個方法裡面我們需要帶入變數在記憶體的位置
你可以使用
fmt.Println(&input)
看看我們到底丟了什麼進去,下面是類似的結果
0xc0000961e0
這個是你電腦記憶體實際上的位置,所以每個人應該都會不太一樣
給方法記憶體位置之後,方法會根據記憶體的位置將值塞進去而不是根據變數名稱
如果聽不懂也沒關係,之後會有重點介紹指標的章節.那時候會再拿出來討論
現在就先這樣記著就好
switch input {
case "1":
var number string
fmt.Println("Please enter the number:")
fmt.Scanln(&number)
fmt.Println(t2h(number))
case "2":
var number string
fmt.Println("Please enter the number:")
fmt.Scanln(&number)
fmt.Println(h2t(number))
default:
fmt.Println("Wrong selection")
}
注意到golang的switch是不需要break的喔
而且不管是if for 還是switch, {都必須放置在第一行
如果你的程式像下面這樣
switch input
{
//something
}
會直接報錯
另外一點跟C#不一樣的是在golang每個case都是各自獨立的
還記得C#我們的switch這樣寫嗎?
case "1":
Console.WriteLine("Please enter the number:");
String number = Console.ReadLine();
t2h(number);
break;
case "2":
Console.WriteLine("Please enter the number:");
number = Console.ReadLine();
h2t(number);
break;
在C#內的case並非完全獨立,在case2你可以使用到case1中宣告出來的變數
但是在golang中如果把case2中的
var number string
給拿掉可是會報錯的喔
func h2t(number string) (output float64){
inarr := strings.Split(number, "")
i:=0
for i < len(inarr) {
output += AEreturn(inarr[i]) * math.Pow(16, float64(len(inarr)-(i+1)))
i++
}
return
}
注意到這一段
inarr := strings.Split(number, "")
:=是golang提供的語法糖,可以自動幫你推斷型別
否則你需要這樣宣告
var inarr []string = strings.Split(number, "")
如同我說的,golang很注重實務面,因此能省幾個字就盡量省
基本上在方法內也很少有人會真的寫var,畢竟:=太好用了
而下一行的
i:=0
因為沒有小數點,因此自動推斷成整數型別(int)
如果你的宣告長這樣
i:=0.0
那他反而會自動推斷成浮點數型別(floag64)
回到第一行
以往我們都可以直接用number[i]來取出字串中的字元
雖然golang也能這麼做,不過golang的字串要當作陣列來使用相對比較麻煩
因此我們用strings(就是我們上面import的那個)提供的方法將字串給切割成單個字串組成的陣列
Split的用法是會根據第二個參數當作切割點去切割字串,這裡我們選擇給空字串則會把字串割成字串陣列(長度為1)
如果你需要用別的切法也可以這樣寫
inarr := strings.Split("1,2,3,4", ",")//會依照","為區隔切開
切完後:=會自動推斷,後面Split輸出的型別為字串陣列[]string,因此他會自動幫前面的inarr宣告成字串陣列型別
你可以有另外一種把字串當作字元陣列的寫法
func h2t(number string) (output float64){
i:=0
for _, char := range number{ //這裡會遍歷number的所有元素(字元),並且放入到char這個變數中,型別為rune
output += AEreturn(string(char)) * math.Pow(16, float64(len(number)-(i+1)))
i++
}
return
}
不過for range的詳細用法要請各位自己去查拉,這裡就不深入
如果你已經試了的話可能會遇到下面的問題
./TenToHex.go:7:2: imported and not used: "strings"
這是由於golang不允許有宣告或引入的函式庫沒有被使用,
因此你可以把import的strings那行給註解或刪掉,就可以正常執行了
接著我們遇到了迴圈
for i < len(inarr) {
output += AEreturn(inarr[i]) * math.Pow(16, float64(len(inarr)-(i+1)))
i++
}
while勒?
怎麼只剩水箭龜怎麼是for?
在golang中,迴圈一律使用for,裡面放的東西跟while一模一樣
所以如果你需要無限迴圈可以這樣寫
for {
//something
}
因為沒有條件所以一定符合,然後就會不斷執行
output += AEreturn(inarr[i]) * math.Pow(16, float64(len(inarr)-(i+1)))
在大部分的語言中,當你使用pow計算時,會自動幫你把輸入的整數型別轉型成double或float型別
但是golang不會這麼做,因此你可以看到我們必須自己將他們轉型
float64(len(inarr)-(i+1))
轉形成float64之後才能放入pow中當參數使用
那為什麼16明明是整數型別卻可以當作pow的參數呢?
理由在於16可以當作是float64或是int,這裡golang自動當作float64了
而型別的理由也讓我們最初宣告要回傳的output時就宣告成float64型別,這樣才能與冪次計算出來的float64型別做計算
我們往下看AEreturn這個方法吧
func AEreturn(number string) (output float64) {
num, err := strconv.Atoi(number)
if err != nil {
switch number {
case "A":
num = 10
case "B":
num = 11
case "C":
num = 12
case "D":
num = 13
case "E":
num = 14
case "F":
num = 15
default:
num = 0
}
}
output = float64(num)
return
}
再複習一下,這裡方法的輸入為字串,輸出為float64
然後注意到這一行
num, err := strconv.Atoi(number)
strconv也是我們最初有import的函式庫,提供了一個Atoi的方法將字串轉型成整數
接著因為轉型是有可能錯誤的(比方說"A"就沒辦法轉型),因此輸出的回傳值會有兩個
第一個num就是轉型成功的值
第二個err則是錯誤的訊息,型別為error(沒錯,在golang錯誤是有型別的)
如果沒有錯誤的話會回傳nil,也就是空值
因此我們在下一行
if err!=nil
利用確認err裡面是否有東西來確認是否有正確轉型
後續的作法跟之前一樣,轉型成功時就直接丟入num
若是轉型失敗則進入到switch階段,繼續判斷到底是哪一個字串,再根據字串去決定最終的數字
最後在回傳時由於pow需要float64型別,因此我們將int轉型並回傳
output = float64(num)
return
而有時你因為確保輸入值是正確的而不做錯誤處理時你可以像是在t2h裡面這麼做
func t2h(number string) string {
tenum, _ := strconv.Atoi(number) //沒有err
還記得我們之前說過golang不能有值被宣告出來卻沒有任何作用吧
這時如果你不想處理err,可以用 _ 代替,golang不會要你處理
func t2h(number string) string {
tenum, _ := strconv.Atoi(number)
for i := 0; i < 16; i++ {
for j := 0; j < 16; j++ {
for k := 0; k < 16; k++ {
if int(math.Pow(16, 2))*i+int(math.Pow(16, 1))*j+int(math.Pow(16, 0))*k == tenum {
return fmt.Sprintf("%s%s%s",returnAE(i),returnAE(j),returnAE(k))
}
}
}
}
return ""
}
for 迴圈基本上跟我們以往使用的一樣,只是原本的var i=0 被換成了i:=0
if 的用法也跟一般的程式語言相同,差別在於不需要使用()將條件包起來
if int(math.Pow(16, 2))*i+int(math.Pow(16, 1))*j+int(math.Pow(16, 0))*k == tenum {
return fmt.Sprintf("%s%s%s",returnAE(i),returnAE(j),returnAE(k))
}
if裡面我們反過來將pow算出來的float64型別轉成int
int(math.Pow(16, 2))*i
如此才可以跟整數型別的tenum做比較
回傳值的
fmt.Sprintf("%s%s%s",returnAE(i),returnAE(j),returnAE(k))
則是fmt提供並非輸出的方法Sprintf,用來將內容格式化成字串
還記得C#的
Console.WriteLine("{0} {1} {2}",returnAE(i),returnAE(j),returnAE(k));
使用的填充字元是{0}
而在golang則是使用%s
不同型別還有不同用法,詳細可以看這裡
最後補充一下
go fmt .
在golang有明確的規定你的程式碼風格,如果不照著走甚至會不讓你編譯
這方面有人覺得好有人覺得壞
好的話是不管你是誰來自哪裡, { } ( ) 的位置都是一樣的
不好的話是有人就希望照自己的風格( { 要自己一行之類的)
現在去打亂你的程式碼(不是要你程式碼亂搬位置,就把空格或tab亂按一通就好)
接著叫go fmt . 自動幫你整理
. 的目的是不指定檔案,所有的go檔都會幫你整理
以上就是golang的基本語法拉
因為出於私心的關係,golang的介紹比其他的語言都還要長(其實是其他語言不像golang這麼熟)
如果你有興趣,可以看隔壁的golang介紹
明天我們來介紹golang的殺手級應用docker
如果有任何寫不清楚或是觀念沒有很明白的話請留言告知我
會盡快補上
如果有任何寫錯的地方也麻煩留言告知我
會盡快修正
感謝各位